# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::VulnerabilityScanning::SecurityReportBuilder, feature_category: :software_composition_analysis do
  let(:report_type) { "dependency_scanning" }
  let(:sbom_source) { build(:ci_reports_sbom_source, input_file_path: "go.mod", source_file_path: "go.mod") }
  let(:sbom) { build(:ci_reports_sbom_report, source: sbom_source) }

  let_it_be(:ci_build) { build(:ci_build) }

  let_it_be(:minio_component) do
    build(:vs_affected_component, name: "github.com/minio/minio", version: "v0.0.0-20180419184637-5a16671f721f",
      purl_type: "golang")
  end

  let_it_be(:cve_2018_1000538) do
    build(:vs_advisory,
      xid: "051e7fdd-4e0a-4dfd-ba52-083ee235a580",
      title: "Allocation of File Descriptors or Handles Without Limits or Throttling",
      description: "Minio a Allocation of Memory Without Limits or Throttling vulnerability in write-to-RAM.",
      cvss_v2: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
      cvss_v3: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      identifiers: [
        build(:pm_identifier, type: "cve", name: "CVE-2018-1000538",
          url: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000538", value: "CVE-2018-1000538")
      ],
      urls: ["https://github.com/minio/minio/pull/5957", "https://nvd.nist.gov/vuln/detail/CVE-2018-1000538"],
      solution: "Unfortunately, there is no solution available yet.",
      affected_component: minio_component
    )
  end

  let_it_be(:cve_2020_11012) do
    build(:vs_advisory,
      xid: "216192fe-2efa-4c52-addd-4bf3522c2b69",
      title: "Improper Authentication",
      description: "MinIO versions before has an authentication bypass issue in the MinIO admin API. " \
                   "Given an admin access key, it is possible to perform admin API operations, i.e., " \
                   "creating new service accounts for existing access keys without knowing the admin secret key.",
      cvss_v2: "AV:N/AC:L/Au:N/C:N/I:P/A:N",
      cvss_v3: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
      identifiers: [
        build(:pm_identifier, type: "cve", name: "CVE-2020-11012",
          url: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11012", value: "CVE-2020-11012")
      ],
      urls: ["https://nvd.nist.gov/vuln/detail/CVE-2020-11012"],
      solution: "Upgrade to version RELEASE.2020-04-23T00-58-49Z or above.",
      affected_component: minio_component
    )
  end

  let(:dependency_scanning_json_report) do
    File.read(
      Rails.root.join('ee/spec/fixtures/security_reports/simple/gl-dependency-scanning-report.json')
    )
  end

  let(:expected) { Gitlab::Ci::Reports::Security::Report.new(report_type, ci_build.pipeline, created_at) }
  let(:created_at) { Time.zone.now }

  subject(:builder) do
    described_class.new(report_type: report_type, project: ci_build.project, pipeline: ci_build.pipeline, sbom: sbom)
  end

  before do
    Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(dependency_scanning_json_report, expected, validate: true)
    builder.add_advisories([cve_2020_11012, cve_2018_1000538])
  end

  describe "#report" do
    context "when components are vulnerable" do
      it "builds a valid report" do
        expect(builder.report.errored?).to eq(false)
        expect(builder.report.warnings?).to eq(false)
      end

      it "adds correct findings" do
        attributes = %i[
          report_type
          project_fingerprint
          compare_key
          uuid
          name
          description
          solution
          identifiers
          flags
          links
          location
          evidence
          severity
          confidence
          details
          signatures
        ].freeze

        convert_to_hash = ->(finding) { finding.to_hash.slice(*attributes) }
        findings = builder.report.findings.map(&convert_to_hash)
        expected_findings = expected.findings.map(&convert_to_hash)

        expect(findings).to match_array(expected_findings)
      end

      it "adds correct identifiers" do
        expect(builder.report.identifiers).to match_array(expected.identifiers)
      end

      it "does not produce or remove findings when compared against analyzer report" do
        comparer = Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(ci_build.project, expected,
          builder.report)
        expect(comparer.added).to be_empty
        expect(comparer.fixed).to be_empty
      end
    end

    context "when report type is container_scanning" do
      let(:report_type) { "container_scanning" }

      it "does not add any findings" do
        expect(builder.report.findings).to be_empty
      end
    end

    context "when supplied cylonedx is incompatible" do
      let(:sbom_source) { build(:ci_reports_sbom_source, data: {}) }

      it "adds an error to the report" do
        expect(builder.report.errored?).to eq(true)
        expect(builder.report.errors).to match_array([
          { type: "MissingPropertiesError",
            message: "Missing required gitlab:dependency_scanning CycloneDX properties" },
          { type: "MissingPropertiesError",
            message: "Missing required gitlab:dependency_scanning CycloneDX properties" }
        ])
      end
    end
  end
end
